home *** CD-ROM | disk | FTP | other *** search
- // -------------------------------------------------------------------------------------
- // Pie Chart view
- // Martin D. Flynn, NeXT Computer, Inc.
- // -------------------------------------------------------------------------------------
-
- #import <appkit/appkit.h>
- #import <libc.h>
- #import <c.h>
- #import <stdlib.h>
- #import <string.h>
- #import <math.h>
- #import <dpsclient/wraps.h>
- #import <objc/List.h>
- #import <objc/Storage.h>
- #import "PieChartPS.h"
- #import "PieChart.h"
-
- // -------------------------------------------------------------------------------------
- // archive version number
- #define VERSION 1
-
- // -------------------------------------------------------------------------------------
- #define X origin.x
- #define Y origin.y
- #define W size.width
- #define H size.height
- #define pieSLICE(I) ((pieSlice_t*)[dataId elementAt:(I)])
- #define matrixCELL(M,I) (pFlags.useCellTags?[M findCellWithTag:I]:[[M cellList] objectAt:I])
- #define ibSliceCOUNT ((dftSliceCount>0)?dftSliceCount:3)
-
- // -------------------------------------------------------------------------------------
- // percent display format
- static char percentFormat[16] = { "%.1f%%" };
-
- // -------------------------------------------------------------------------------------
- // pie slice data
- typedef struct pieSlice_s {
- id labelSrc; // label source for slice
- const char *label; // slice label
- id valueSrc; // value source for slice
- float value; // current slice value
- float percent; // current slice percent
- float endAngle; // ending angle
- } pieSlice_t;
-
- // -------------------------------------------------------------------------------------
- @implementation PieChart
-
- // -------------------------------------------------------------------------------------
- // archive version number
- static BOOL _nibMode = NO;
- + initialize
- {
- [self setVersion:VERSION];
- if (!strcmp([NXApp appName], "InterfaceBuilder")) _nibMode = YES;
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // return inspector name
- - (const char*)getInspectorClassName { return "PieChartInspector"; }
-
- // -------------------------------------------------------------------------------------
- // color/gray support
-
- /* convert color to gray */
- static float _colorGray(NXColor color)
- {
- float gray;
- NXConvertColorToGray(color, &gray);
- return gray;
- }
-
- /* set color/gray */
- static void _setColor(NXColor color, BOOL isColor)
- {
- if (isColor) NXSetColor(color); else PSsetgray(_colorGray(color));
- }
-
- // -------------------------------------------------------------------------------------
- // color table support
-
- /* return gray value for slice number */
- static float _grayValue(int i, int n)
- {
- if ((++i) & 1) return (float)(i + 1) / (float)(2 * n);
- return (((float)i / 2.0) + (float)((n + 1) / 2)) / (float)n;
- }
-
- - _initColorTable:(int)count clear:(BOOL)clear
- {
- int i;
-
- /* clear existing table */
- if (clear && sliceColors) { [sliceColors free]; sliceColors = (id)nil; }
-
- /* set default slice count */
- dftSliceCount = (count >= 0)? count : 0;
- if (dftSliceCount <= 0) return self;
-
- /* build table */
- if (!sliceColors) {
- sliceColors = [[[Storage alloc] initCount:1 elementSize:sizeof(NXColor)
- description:(char*)nil] empty];
- }
-
- /* fill table */
- for (i = [sliceColors count]; i < dftSliceCount; i++) {
- NXColor color = NXConvertGrayToColor(_grayValue(i, dftSliceCount));
- [sliceColors addElement:&color];
- }
-
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // initialize data
-
- /* empty dataId */
- - _initPieData
- {
- int i;
-
- /* create data holder if non-existant */
- if (!dataId) {
- dataId = [Storage newCount:1 elementSize:sizeof(pieSlice_t) description:(char*)nil];
- [dataId empty];
- }
-
- /* free any existing data */
- if (i = [dataId count]) {
- while (i) {
- const char *lbl = pieSLICE(--i)->label;
- if (lbl) free((char*)lbl);
- }
- [dataId empty];
- }
-
- return dataId;
- }
-
- /* fill dataId */
- - _fillPieData:(int)count
- {
- int i;
- for (i = 0; i < count; i++) [self setValue:100.0 forSlice:i];
- return dataId;
- }
-
- /* IB use (_nibMode assumed): fill pie with default data */
- - _initDefaultPie:(int)sliceCount
- {
- [self _initPieData]; // clear existing data
- [self _fillPieData:((sliceCount>=0)?sliceCount:ibSliceCOUNT)];
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // initialize view
-
- /* main init */
- - initFrame:(const NXRect*)r
- {
-
- /* init view */
- [super initFrame:r];
-
- /* init flags */
- memset(&pFlags, 0, sizeof(pFlags));
- pFlags.showPercent = YES;
- pFlags.drawOutline = YES;
- pFlags.drawLabels = YES;
- pFlags.drawInColor = YES;
- pFlags.acceptColor = NO;
-
- /* initialize with defaults */
- outlineColor = NXConvertGrayToColor(NX_BLACK);
- labelColor = NXConvertGrayToColor(NX_BLACK);
- backgroundColor = NXConvertGrayToColor(NX_LTGRAY);
- currentFont = [Font newFont:"Helvetica" size:10.0 matrix:NX_IDENTITYMATRIX];
- labelMatrix = (id)nil;
- valueMatrix = (id)nil;
- outputPercent = (id)nil;
- delegate = self;
-
- /* init data holder */
- dataId = (id)nil;
- sliceColors = (id)nil;
- [self setDefaultSliceCount:0];
- [self _initPieData];
- if (_nibMode) [self _fillPieData:ibSliceCOUNT];
-
- return self;
- }
-
- /* free pie chart */
- - free
- {
- [[self _initPieData] free];
- [self setDefaultSliceCount:0];
- return [super free];
- }
-
- // -------------------------------------------------------------------------------------
- // adding data to the pie chart
-
- /* return cell title */
- static const char *_cellTitle(id cellId)
- {
- const char *label = (const char*)nil;
- if (!cellId) return (const char*)nil;
- if ([cellId respondsTo:@selector(title)]) label = [cellId title]; else
- if ([cellId respondsTo:@selector(stringValue)]) label = [cellId stringValue];
- return label;
- }
-
- /* fill holes in data */
- static void _fillData(id dataId, int limit)
- {
- int i;
- pieSlice_t data = { (id)nil, (const char*)nil, (id)nil, 0.0, 0.0, 0.0 };
- for (i = [dataId count]; i <= limit; i++) [dataId addElement:&data];
- }
-
- /* set pie slice data */
- - _setPieSlice:(int)slice :(id)vs:(float)v:(BOOL)setV :(id)ls:(char*)l:(BOOL)setL
- {
- pieSlice_t *pieSlice;
-
- /* fill up any holes in data */
- if (slice < 0) return (id)nil;
- _fillData(dataId, slice);
- pieSlice = pieSLICE(slice);
-
- /* set label */
- if (setL) {
- if (pieSlice->label) free((char*)pieSlice->label);
- pieSlice->labelSrc = ls;
- pieSlice->label = (!ls && l)? NXCopyStringBuffer(l) : (const char*)nil;
- }
-
- /* set value */
- if (setV) {
- pieSlice->valueSrc = vs;
- pieSlice->value = (!vs)? v : 0.0;
- }
-
- return self;
- }
-
- /* explicitly set a pie slice value (no re-display) */
- - setValue:(float)value forSlice:(int)slice
- {
- return [self _setPieSlice:slice :(id)nil:value:YES :(id)nil:(char*)nil:NO];
- }
-
- /* set pie slice value and label */
- - setValue:(float)value andLabel:(const char*)label forSlice:(int)slice
- {
- return [self _setPieSlice:slice :(id)nil:value:YES :(id)nil:(char*)label:YES];
- }
-
- /* redisplay */
- - update:sender
- {
- return [self display];
- }
-
- // -------------------------------------------------------------------------------------
- // set plot options
- // -------------------------------------------------------------------------------------
-
- /* set background transparent */
- - setBackgroundTransparent:(BOOL)flag
- {
- pFlags.transparent = flag?YES:NO;
- return self;
- }
-
- /* return background transparent state */
- - (BOOL)backgroundTransparent
- {
- return pFlags.transparent;
- }
-
- /* set font (make a new unflipped font copy) */
- // This is done this way on purpose
- - setFont:fontId
- {
- currentFont = [Font newFont:[fontId name] size:[fontId pointSize] matrix:NX_IDENTITYMATRIX];
- return self;
- }
-
- /* return current font (NX_FLIPPEDMATRIX) */
- // This is done this way on purpose (IB can't handle it properly any other way)
- - font
- {
- return [Font newFont:[currentFont name] size:[currentFont pointSize]];
- }
-
- /* set background gray */
- - setBackgroundGray:(float)aGray
- {
- backgroundColor = NXConvertGrayToColor(aGray);
- return self;
- }
-
- /* return current background gray */
- - (float)backgroundGray
- {
- float gray;
- NXConvertColorToGray(backgroundColor, &gray);
- return gray;
- }
-
- /* set background color */
- - setBackgroundColor:(NXColor)aColor
- {
- backgroundColor = aColor;
- return self;
- }
-
- /* return current background color */
- - (NXColor)backgroundColor
- {
- return backgroundColor;
- }
-
- /* set outline gray */
- - setOutlineGray:(float)aGray
- {
- outlineColor = NXConvertGrayToColor(aGray);
- return self;
- }
-
- /* return current outline gray */
- - (float)outlineGray
- {
- float gray;
- NXConvertColorToGray(outlineColor, &gray);
- return gray;
- }
-
- /* set text label gray */
- - setLabelGray:(float)aGray
- {
- labelColor = NXConvertGrayToColor(aGray);
- return self;
- }
-
- /* return current label gray */
- - (float)labelGray
- {
- float gray;
- NXConvertColorToGray(labelColor, &gray);
- return gray;
- }
-
- /* set show percent on pie slice */
- - setShowPercent:(BOOL)flag
- {
- pFlags.showPercent = flag?YES:NO;
- return self;
- }
-
- /* return show percent status */
- - (BOOL)showPercent
- {
- return pFlags.showPercent;
- }
-
- /* set pie slice outline */
- - setDrawSliceOutline:(BOOL)flag
- {
- pFlags.drawOutline = flag?YES:NO;
- return self;
- }
-
- /* return pie slice outline status */
- - (BOOL)drawSliceOutline
- {
- return pFlags.drawOutline;
- }
-
- /* set draw label */
- - setDrawLabels:(BOOL)flag
- {
- pFlags.drawLabels = flag?YES:NO;
- return self;
- }
-
- /* return draw label status */
- - (BOOL)drawLabels
- {
- return pFlags.drawLabels;
- }
-
- /* set specific slice color */
- - setSliceColor:(NXColor)color at:(int)slice
- {
- if (slice < 0) return self;
- if (slice + 1 > dftSliceCount) [self _initColorTable:slice + 1 clear:NO];
- [sliceColors replaceElementAt:slice with:&color];
- return self;
- }
-
- /* return slice color */
- - (NXColor)sliceColorAt:(int)slice
- {
- int count;
-
- /* check color table */
- if (sliceColors && (slice < [sliceColors count])) {
- NXColor color = *((NXColor*)[sliceColors elementAt:slice]);
- return color;
- }
-
- /* return gray */
- count = (dftSliceCount > 0)? dftSliceCount : (dataId? [dataId count] : 0);
- if (slice >= count) return NXConvertGrayToColor(NX_WHITE);
- return NXConvertGrayToColor(_grayValue(slice, count));
-
- }
-
- /* set default slice count */
- - setDefaultSliceCount:(int)count
- {
- return [self _initColorTable:count clear:YES];
- }
-
- /* return current default slice count */
- - (int)defaultSliceCount
- {
- return dftSliceCount;
- }
-
- /* allow accepting color changes */
- - setAcceptColor:(BOOL)flag
- {
- pFlags.acceptColor = flag;
- if (flag) pFlags.drawInColor = YES;
- return self;
- }
-
- /* set delegate */
- - setDelegate:aDelegate
- {
- delegate = aDelegate;
- return self;
- }
-
- /* return delegate */
- - delegate
- {
- return delegate;
- }
-
- // -------------------------------------------------------------------------------------
- // outlets
- // -------------------------------------------------------------------------------------
-
- /* load value and label matrix into pie */
- - _loadMatrix:sender
- {
- int i;
- id vList = valueMatrix? [valueMatrix cellList] : (id)nil;
- id lList = labelMatrix? [labelMatrix cellList] : (id)nil;
- [self _initPieData]; // clear existing data
- if (!vList) return self;
- for (i = 0; i < [vList count]; i++) {
- id vCell = [vList objectAt:i];
- id lCell = (lList && (i < [lList count]))? [lList objectAt:i] : (id)nil;
- [self _setPieSlice:(pFlags.useCellTags?[vCell tag]:i)
- :vCell:0.0:YES :lCell:(char*)nil:YES];
- }
- [self display];
- return self;
- }
-
- /* set value matrix */
- - setValueMatrix:anObject
- {
- if (![anObject isKindOf:[Matrix class]]) return self;
- valueMatrix = anObject;
- [self perform:@selector(_loadMatrix:) with:(id)nil afterDelay:1 cancelPrevious:YES];
- return self;
- }
-
- /* set label matrix */
- - setLabelMatrix:anObject
- {
- if (![anObject isKindOf:[Matrix class]]) return self;
- labelMatrix = anObject;
- [self perform:@selector(_loadMatrix:) with:(id)nil afterDelay:1 cancelPrevious:YES];
- return self;
- }
-
- /* set form matrix */
- - setFormMatrix:anObject
- {
- if (![anObject isKindOf:[Matrix class]]) return self;
- labelMatrix = valueMatrix = anObject;
- [self perform:@selector(_loadMatrix:) with:(id)nil afterDelay:1 cancelPrevious:YES];
- return self;
- }
-
- /* set output percent matrix */
- - setPercentMatrix:anObject
- {
- if (![anObject isKindOf:[Matrix class]]) return self;
- outputPercent = anObject;
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // plot data in pie chart
- // -------------------------------------------------------------------------------------
-
- /* pie chart plot */
- - _drawPie
- {
- int i, dataCnt = [dataId count];
- float sum, curAngle, angle;
-
- /* set fonts and gray */
- [currentFont set];
- PSsetlinewidth(1.0);
-
- /* determine center and radius */
- pieCenter.x = bounds.W / 2.0;
- pieCenter.y = bounds.H / 2.0;
- if (!pFlags.drawLabels) pieRadius = (MIN(bounds.W, bounds.H) - 2.0) / 2.0;
- else pieRadius = (MIN(bounds.W, bounds.H) - (4.5 * [currentFont pointSize])) / 2.0;
- if (pieRadius < 1.0) pieRadius = 1.0;
-
- /* update values and sum */
- for (sum = 0.0, i = 0; i < dataCnt; i++) {
- pieSlice_t *slice = pieSLICE(i);
- if (slice->valueSrc) slice->value = [slice->valueSrc floatValue];
- sum += slice->value;
- }
-
- /* draw pie ('slice->value' is current) */
- if (sum) {
- char *lp, label[256], pct[16];
- float oGray, lGray;
-
- /* draw slices */
- for (curAngle = 0.0, i = 0; i < dataCnt; i++) {
- pieSlice_t *slice = pieSLICE(i);
- slice->percent = slice->value * 100.0 / sum;
- angle = slice->percent * 3.60;
- slice->endAngle = curAngle + angle;
- if (!angle) continue;
- if (lp = (char*)_cellTitle(slice->labelSrc)) strcpy(label, lp); else
- if (slice->label) strcpy(label, slice->label);
- else sprintf(label, "#%d", i + 1);
- if (pFlags.showPercent) {
- sprintf(pct, percentFormat, slice->percent);
- sprintf(&label[strlen(label)], " (%s)", pct);
- }
- _setColor([self sliceColorAt:i], pFlags.drawInColor);
- NXConvertColorToGray(outlineColor, &oGray);
- NXConvertColorToGray(labelColor, &lGray);
- _pieDrawSlice(label, [currentFont pointSize], pieCenter.x, pieCenter.y, pieRadius,
- curAngle, slice->endAngle, (pFlags.drawOutline?oGray:-1.0),
- (pFlags.drawLabels?lGray:-1.0));
- curAngle += angle;
- }
-
- /* update output percent matrix */
- if (outputPercent) {
- for (i = 0; i < dataCnt; i++) {
- id cellId = matrixCELL(outputPercent, i);
- if (cellId) {
- pieSlice_t *slice = pieSLICE(i);
- sprintf(pct, percentFormat, slice->percent);
- [cellId setStringValue:pct];
- }
- }
- }
-
- }
-
- return self;
-
- }
-
- /* draw pie view */
- - drawSelf:(const NXRect *)r :(int)n
- {
- if (!pFlags.transparent) {
- _setColor(backgroundColor, pFlags.drawInColor);
- NXRectFill(&bounds);
- }
- [self _drawPie];
- //if (isFirstResponder) NXHighlightRect(&bounds);
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // object archiving
-
- - read:(NXTypedStream*)s
- {
- int ver, i, cnt;
- sliceColors = (id)nil;
- [super read:s];
- NXReadTypes(s, "i",&ver);
- NXReadTypes(s, "s@",&pFlags,¤tFont);
- backgroundColor = NXReadColor(s);
- outlineColor = NXReadColor(s);
- labelColor = NXReadColor(s);
- NXReadTypes(s, "i",&cnt);
- [self setDefaultSliceCount:cnt]; // set dftSliceCount
- for (i = 0; i < dftSliceCount; i++) [self setSliceColor:NXReadColor(s) at:i];
- delegate = self;
- labelMatrix = (id)nil;
- valueMatrix = (id)nil;
- outputPercent = (id)nil;
- dataId = (id)nil;
- pFlags.drawInColor = YES;
- [self _initPieData];
- if (_nibMode) [self _fillPieData:ibSliceCOUNT];
- return self;
- }
-
- - write:(NXTypedStream*)s
- {
- int ver = [[self class] version];
- int i;
- [super write:s];
- NXWriteTypes(s, "i",&ver);
- NXWriteTypes(s, "s@",&pFlags,¤tFont);
- NXWriteColor(s, backgroundColor);
- NXWriteColor(s, outlineColor);
- NXWriteColor(s, labelColor);
- NXWriteTypes(s, "i",&dftSliceCount);
- for (i = 0; i < dftSliceCount; i++) NXWriteColor(s, [self sliceColorAt:i]);
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // firstResponder for getting copy/paste commands
-
- /* allow becoming first responder */
- - (BOOL)acceptsFirstResponder { return YES; }
-
- /* become first responder */
- - becomeFirstResponder
- {
- isFirstResponder = YES;
- [self display];
- return self;
- }
-
- /* resign first responder */
- - resignFirstResponder
- {
- isFirstResponder = NO;
- [self display];
- return self;
- }
-
- // -------------------------------------------------------------------------------------
- // mouseDown tracking
-
- /* handle pie slice selected */
- - selectedPieSlice:(int)slice
- {
- id cellId, matrix = actionMatrix;
- if (!matrix && valueMatrix && ([valueMatrix target] != self)) matrix = valueMatrix;
- if (matrix && (slice >= 0) && (cellId = matrixCELL(matrix, slice))) {
- [matrix selectCell:cellId];
- [matrix sendAction];
- }
- return self;
- }
-
- /* slice hit detection */
- - (int)_sliceHitDetection:(const NXPoint*)mousePoint
- {
- int index, dataCnt = dataId? [dataId count] : 0;
- float len, xLen, yLen, angle;
- NXPoint pt = *mousePoint;
-
- /* check for valid pie */
- if (!dataCnt) return -1;
- [self convertPoint:&pt fromView:(id)nil];
-
- /* determine distance from center */
- xLen = pt.x - pieCenter.x;
- yLen = pt.y - pieCenter.y;
- len = (float)hypot((double)xLen, (double)yLen);
- if (len > pieRadius) return -1;
-
- /* determine angle */
- angle = (float)(atan2(yLen, xLen) * 180.0 / M_PI);
- if (angle < 0.0) angle += 360.0;
-
- /* find pie slice */
- for (index = 0; index < dataCnt; index++) {
- pieSlice_t *slice = pieSLICE(index);
- if (angle <= slice->endAngle) return index;
- }
-
- return -1;
- }
-
- /* intercept mouse down */
- - mouseDown:(NXEvent*)e
- {
- int index;
-
- /* IB check */
- if (_nibMode && (delegate != self))
- return [delegate selectedPieSlice:[self _sliceHitDetection:&(e->location)]];
-
- /* check for slice selection */
- if (e->data.mouse.click == 2) {
- index = [self _sliceHitDetection:&(e->location)];
- if ((index >= 0) && [delegate respondsTo:@selector(selectedPieSlice:)])
- [delegate selectedPieSlice:index];
- }
-
- return [super mouseDown:e];
- }
-
- // -------------------------------------------------------------------------------------
- // IB use: color modification
-
- /* accept color */
- - acceptColor:(NXColor)color atPoint:(const NXPoint*)point
- {
- int slice;
- if (!pFlags.acceptColor) return self;
- slice = [self _sliceHitDetection:point];
- if (slice < 0) [self setBackgroundColor:color];
- else if (dftSliceCount > 0) [self setSliceColor:color at:slice];
- return [self display];
- }
-
- @end
-